/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/

package keystoreone;

import com.google.common.base.Strings;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
// bcprov-ext-jdk15on-150.jar
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMWriter;

/**
 * Clase que resume las diferentes operaciones en la generación de certificados digitales
 * @author rmartinezch
 * @version 0.9
 */
public class KeyStoreOne {
    
    /**
     * @param args the command line arguments
     * @throws java.security.KeyStoreException
     * @throws java.io.IOException
     * @throws java.security.NoSuchAlgorithmException
     * @throws java.security.cert.CertificateException
     */
    
    
    static String sourcePath    = "D:/Data/Projects/certificates/";
    /**
     * certNameIn: certificado binario exportado con el aplicativo certmgr.msc de Windows
     * También ha tenido que hacerse público para ser publicado en el doc html resultante
     */
    public static String certNameIn    = "GS_CD_exp.cer";              // certificado binario exportado con certmgr.msc
    static String pathName      = "D:/Data/";                   // Ruta genérica de locación de archivos
    static String certName      = "CD_GlobalCert.cer";          // certificado existente
    static String ksFileName    = "CD_KS";                      // Nombre del archivo (contenedor de claves)
    static String kspw          = "12345678";                   // password del archivo contenedor de claves
    static String alias         = "CertificationDepartment";    // alias del certificado contenido en el keystore
    static String certFileName  = "GS_CD_wrt.der";              // archivo donde se guardará el certificado digital exportado
    static String p12FileName   = "mpaula3.p12";                // archivo contenedor pkcs#12
    static String p12pw         = "mpaula3";                    // clave del contenedor pkcs#12
    static String pfxFileName   = "GS_CD_exp.pfx";              // Contenedor pfx
    static String pfxpw         = "fox0dana";                   // pw del contenedor pfx
    
    static PrivateKey   privateKey  = null;
    static Certificate  certificate = null;
    
    static {
        java.security.Security.addProvider(new BouncyCastleProvider());
    }
    
    /**
     * Metodo main de la clase
     * @param args
     * @throws KeyStoreException
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws CertificateException
     * @throws NoSuchProviderException
     * @throws UnrecoverableKeyException
     */
    public static void main(String[] args) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, UnrecoverableKeyException {
        // TODO code application logic here
        
        // creando un JKS
//        createKeyStore();
        // exportando un certificado desde un contenedor existente
//        exportCertificateFromKeyStoreToFile();
        // lectura de los alias contenidos en un contenedor pkcs#12
        readContentFromKeyStore();
        // escribe la clave privada leida
//        writeToFile(false, privateKey.getEncoded(), sourcePath + "pk.der");
    }
    
    /**
     * Método que crea un contenedor de claves
     * Éste es un método comentado para javadoc, método que ha tenido que hacerse público para ser publicado en el html
     * @throws java.security.KeyStoreException
     * @throws java.io.IOException
     * @throws java.security.NoSuchAlgorithmException
     * @throws java.security.cert.CertificateException
    */
    public static void createKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException{
        String store = "JKS";
        KeyStore ks = KeyStore.getInstance(store);
        ks.load(null, null);    // origen y password nulos - keystore vacio
        // el certificado
        
//        FileInputStream fis = new FileInputStream(pathName + certName);
        FileInputStream fis = new FileInputStream(sourcePath + certNameIn);
        
        BufferedInputStream bis = new BufferedInputStream(fis);
        
        // instancia de un certificado x.509
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        
        // certificado cert.Certificate
        Certificate cert = null;
        
        // añadir el certificado al keystore y darle un alias
        while ( bis.available() > 0 )
        {
            cert = cf.generateCertificate( bis );
            ks.setCertificateEntry(alias, cert);
        }
//        ks.setCertificateEntry(alias, cert);
        
        // guardar el keystore en un archivo
//        ks.store(new FileOutputStream(pathName + ksFileName), kspw.toCharArray());
        ks.store(new FileOutputStream(sourcePath + ksFileName), kspw.toCharArray());
    }
    
    // obtiene un certificado desde un contenedor y lo guarda en un archivo
    static void exportCertificateFromKeyStoreToFile() throws FileNotFoundException, KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException{
        // archivo contenedor de claves como flujo de entrada
//        FileInputStream fis = new FileInputStream(pathName + ksFileName);
        FileInputStream fis = new FileInputStream(sourcePath + ksFileName);
        // instancia de un contenedor de claves
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        // cargamos el contenedor de claves en la instancia
        ks.load(fis, kspw.toCharArray());
        // obteniendo el certificado del contenedor de claves con el alias
        Certificate cert = ks.getCertificate(alias);
        // el certificado como un buffer de arrays de bytes
        byte[] buf = cert.getEncoded();
        // archivo a guardar
        writeToFile(false, buf, sourcePath + certFileName);
        /*
        File file = new File(pathName + certFileName);
        try (FileOutputStream fos = new FileOutputStream(file)) {
        fos.write(buf);
        fos.flush();
        }*/
    }
    
    static void readContentFromKeyStore() throws FileNotFoundException, KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, UnrecoverableKeyException{
        // contenedor de claves como flujo de entrada
//        System.out.println("store :" + pathName + pfxFileName);
//        FileInputStream fis = new FileInputStream(pathName + p12FileName);
        FileInputStream fis = new FileInputStream(sourcePath + pfxFileName);
        
        // instancia de un contenedor de claves
        KeyStore ks = KeyStore.getInstance("PKCS12");
//        KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
//        KeyStore ks = KeyStore.getInstance("pkcs12", "SunJSSE");
//        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        
        // cargamos el flujo en la instancia
//        ks.load(fis, p12pw.toCharArray());
        ks.load(fis, pfxpw.toCharArray());
        
        // lectura de los alias contenidos en el contenedor
        Enumeration<String> aliases = ks.aliases();
        while(aliases.hasMoreElements()){
            String aliasOne = aliases.nextElement();
            if (ks.isCertificateEntry(aliasOne)) {
                System.out.println("cer entry : " + aliasOne);
            } else if (ks.isKeyEntry(aliasOne)) {
                System.out.println("key entry : " + aliasOne);
                System.out.println("Subject   : " + (((X509Certificate)ks.getCertificate(aliasOne)).getSubjectDN()));
                privateKey = (PrivateKey)ks.getKey(aliasOne, pfxpw.toCharArray());
                certificate = ks.getCertificate(aliasOne);
            }
        }
        
        // imprimiendo en sout en formato pem
        printPEMFormat(certificate);
        printPEMFormat(privateKey);
        
        writeToFile(false, privateKey.getEncoded(), sourcePath + "pk.der");
    }
    
    static void printPEMFormat(Object o) throws IOException{
        PEMWriter pemWriter = new PEMWriter(new PrintWriter(System.out));
        
        System.out.println(Strings.repeat("=", 80));
        System.out.println("PEM format");
        System.out.println(Strings.repeat("=", 80));
        System.out.println();
        pemWriter.writeObject(o);
        pemWriter.flush();
        System.out.println();
    }
    
    static void writeToFile(boolean mode, byte[] buf, String name) throws FileNotFoundException, IOException{
        File file = new File(name);
        try (FileOutputStream fos = new FileOutputStream(file)) {
            if ( mode ) {
                fos.write(buf); // write in binary mode
                fos.flush();
            } else {
                try (Writer wr = new OutputStreamWriter(fos, Charset.forName("UTF-8"))) {
                    wr.write("-----BEGIN CERTIFICATE-----\n");
                    wr.write(new sun.misc.BASE64Encoder().encode(buf)); // write in text mode
                    wr.write("\n-----END CERTIFICATE-----\n");
                    wr.flush();
                }
                
            }
            fos.close();
        } catch (IOException e) {
            System.out.println("IOException :" + e.getMessage());
        }
    }
}